package com.yifeng.repo.base.security.proxy;

import com.gomcarter.frameworks.base.common.AssertUtils;
import com.gomcarter.frameworks.base.exception.CustomException;
import com.gomcarter.frameworks.base.json.JsonObject;
import com.google.common.base.Strings;
import com.talkyun.utils.revised.http.HttpConnector;
import com.talkyun.utils.revised.http.HttpConnectorFactory;
import com.talkyun.utils.revised.http.HttpResult;
import com.yifeng.repo.base.constant.BaseConstant;
import com.yifeng.repo.base.params.ConsoleParam;
import com.yifeng.repo.base.security.auth.constant.SecurityConstant;
import com.yifeng.repo.base.security.context.manager.RequestContextManager;
import com.yifeng.repo.base.utils.converter.JacksonHelper;

import java.io.Serializable;
import java.util.*;

/**
 * 当前调用服务端接口的实现方案，相当于变相的Feign客户端。
 * 待定：进一步优化可以使用动态代理InvocationHandler ，代价就是增加了复杂度。
 * 【简单的可能反而是最可靠的】
 * Created by daibing on 2020/12/8
 */
public class CommonProxy {
    protected static final HttpConnectorFactory CONNECTOR_FACTORY = new HttpConnectorFactory();
    protected final ConsoleParam consoleParam;

    public CommonProxy(ConsoleParam consoleParam) {
        this.consoleParam = consoleParam;
    }

    protected String buildQueryParameters(Map<String, String> params) {
        if (params == null || params.isEmpty()) {
            return BaseConstant.EMPTY;
        }
        StringBuilder sb = new StringBuilder();
        params.forEach((k, v) -> {
            if (v == null) {
                return;
            }
            sb.append("&").append(k).append("=").append(v);
        });
        return sb.length() > 0 ? sb.substring(1) : BaseConstant.EMPTY;
    }

    protected String buildSingleQueryParameters(String fieldName, Object fieldValue) {
        if (Strings.isNullOrEmpty(fieldName) || fieldValue == null) {
            return BaseConstant.EMPTY;
        }
        return String.format("%s=%s", fieldName, fieldValue);
    }

    protected String buildCollectionQueryParameters(String fieldName, Collection<?> fieldValues) {
        return buildCollectionQueryParameters(fieldName, fieldValues, false);
    }

    protected String buildCollectionQueryParameters(String fieldName, Collection<?> fieldValues, boolean reservePrefix) {
        if (Strings.isNullOrEmpty(fieldName) || fieldValues == null || fieldValues.isEmpty()) {
            return BaseConstant.EMPTY;
        }
        StringBuilder sb = new StringBuilder();
        fieldValues.forEach(value -> sb.append("&").append(fieldName).append("=").append(value));
        return reservePrefix ? sb.toString() : sb.substring(1);
    }

    @SuppressWarnings("unchecked")
    protected <T> List<T> doListCallByRpc(String url, Class<T> clz) {
        HttpResult result = this.doInvoke(BaseConstant.HTTP_GET_METHOD, url, null);
        AssertUtils.isTrue(result.success(), String.format("Get failed，url：%s, status: %s, error: %s", url, result.getStatus(), result.getResult()));

        JsonObject jsonObject = JacksonHelper.toObj(result.getResult(), JsonObject.class);
        AssertUtils.isTrue(jsonObject.getCode() == 0, new CustomException(jsonObject.getCode(), jsonObject.getMessage()));

        if (!(jsonObject.getData() instanceof List)) {
            return Collections.emptyList();
        }
        if (Serializable.class.isAssignableFrom(clz)) {
            return JacksonHelper.toObjList4Serializable(((List<Serializable>) jsonObject.getData()), clz);
        } else {
            return JacksonHelper.toObjList(((List<Map<String, Object>>) jsonObject.getData()), clz);
        }
    }

    @SuppressWarnings("unchecked")
    protected <T> T doGetCallByRpc(String url, Class<T> clz) {
        HttpResult result = this.doInvoke(BaseConstant.HTTP_GET_METHOD, url, null);
        AssertUtils.isTrue(result.success(), String.format("Get failed，url：%s, status: %s, error: %s", url, result.getStatus(), result.getResult()));

        JsonObject jsonObject = JacksonHelper.toObj(result.getResult(), JsonObject.class);
        AssertUtils.isTrue(jsonObject.getCode() == 0, new CustomException(jsonObject.getCode(), jsonObject.getMessage()));

        if (jsonObject.getData() == null) {
            return null;
        }
        // 兼容技术框架：当前请求响应数据，在CustomMappingJacksonConverter类中对Long字段转换为字符串
        if (clz == Long.class && jsonObject.getData().getClass() == String.class) {
            return (T) Long.valueOf((String) jsonObject.getData());
        }
        if (Serializable.class.isAssignableFrom(clz)) {
            return (T) jsonObject.getData();
        }
        return JacksonHelper.toObj((Map<String, Object>) jsonObject.getData(), clz);
    }

    @SuppressWarnings("unchecked")
    protected <T> T doActionCallByRpc(String url, String body, Class<T> clz) {
        HttpResult result = this.doInvoke(BaseConstant.HTTP_POST_METHOD, url, body);
        AssertUtils.isTrue(result.success(), String.format("Post failed，url：%s, status: %s, error: %s", url, result.getStatus(), result.getResult()));

        JsonObject jsonObject = JacksonHelper.toObj(result.getResult(), JsonObject.class);
        AssertUtils.isTrue(jsonObject.getCode() == 0, new CustomException(jsonObject.getCode(), jsonObject.getMessage()));

        if (jsonObject.getData() == null) {
            return null;
        }
        // 兼容技术框架：当前请求响应数据，在CustomMappingJacksonConverter类中对Long字段转换为字符串
        if (clz == Long.class && jsonObject.getData().getClass() == String.class) {
            return (T) Long.valueOf((String) jsonObject.getData());
        }
        if (Serializable.class.isAssignableFrom(clz)) {
            return (T) jsonObject.getData();
        }
        return JacksonHelper.toObj((Map<String, Object>) jsonObject.getData(), clz);
    }

    protected HttpResult doInvoke(String url, String body) {
        return doInvoke(BaseConstant.HTTP_GET_METHOD, url, body);
    }

    protected HttpResult doInvoke(String method, String url, String body) {
        Map<String, String> header = new HashMap<>(3);
        header.put(SecurityConstant.REQ_APP_ID, consoleParam.getAppId());
        if (!Strings.isNullOrEmpty(consoleParam.getAccessToken())) {
            header.put(SecurityConstant.AUTH_TOKEN_HEADER_KEY, consoleParam.getAccessToken());
        }
        if (consoleParam.isNeedSecurityToken()) {
            header.put(SecurityConstant.TOKEN, RequestContextManager.getToken());
        }
        for (int i = 0; i < consoleParam.getTryTimes(); i++) {
            try {
                HttpConnector conn = CONNECTOR_FACTORY.getConnector(url, consoleParam.getClientTimeoutMillis());
                return conn.sendWithHeader(method, header, body);
            } catch (Throwable t) {
                if (consoleParam.getTryTimes() - 1 > i) {
                    // doSleep(1000);
                    continue;
                }
                throw new CustomException(String.format("invoke failed: url=%s, body=%s, error=%s", url, body, t.getClass().getName()));
            }
        }
        throw new CustomException("invoke failed: url=" + url + ", body=" + body);
    }
}
